篇首语:本文由编程笔记#小编为大家整理,主要介绍了重读Spring系列-Conditional注入Bean的判断相关的知识,希望对你有一定的参考价值。
这一篇我们要梳理的主要是关于注入Bean
的条件判断,只有满足对应的条件才注入对应的BeanDefinition
。关于注入的条件判断目前我们知道是有3个地方,其中两个都是使用的ConditionEvaluator
处理,另外一个是我们的上一篇提到的AutoConfigurationImportFilter
。下面我们就按顺序来依次梳理。建议看下前两篇
@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional
Class<? extends Condition>[] value();
首先我们需要了解这个注解&#xff0c;这个注解就是用来条件判断的&#xff0c;只有满足条件(通过value
导入的继承Condition
接口的类来判断)&#xff0c;才导入这个Bean
。下面我们通过一个spring
源码中的demo来看下这个注解的具体配套使用。
&#64;Configuration
&#64;Conditional(NoBeanOneCondition.class)
static class BeanTwoConfiguration
&#64;Bean
public ExampleBean bean2()
return new ExampleBean();
这个配置类BeanTwoConfiguration
&#xff0c;我们可以看到其本身是要注入bean2()
(ExampleBean
)的&#xff0c;但其加了一个&#64;Conditional(NoBeanOneCondition.class)
&#xff0c;就是说这个配置类能不能注入&#xff0c;需要看NoBeanOneCondition
的判断。
static class NoBeanOneCondition implements Condition
&#64;Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
return !context.getBeanFactory().containsBeanDefinition("bean1");
这个判断就是看当前Bean
工厂中有没有名称为bean1
的BeanDefiniton
&#xff0c;如果没有就返回true
&#xff0c;这样就会注入BeanTwoConfiguration
以此来注入其中的ExampleBean
。
&#64;Test
public void conditionalOnMissingBeanNoMatch() throws Exception
AnnotationConfigApplicationContext ctx &#61; new AnnotationConfigApplicationContext();
ctx.register(BeanTwoConfiguration.class);
ctx.refresh();
assertFalse(ctx.containsBean("bean1"));
assertTrue(ctx.containsBean("bean2"));
assertTrue(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration"));
这个就是测试类&#xff0c;可以看到其只ctx.register(BeanTwoConfiguration.class)
&#xff0c;即只注入了BeanTwoConfiguration
&#xff0c;就还没有bean1
&#xff0c;所以这里就会成功注入bean2
(ExampleBean
)。
在上面的demo1基础上&#xff0c;我们来看下另一种情况&#xff1a;
&#64;Configuration
static class BeanOneConfiguration
&#64;Bean
public ExampleBean bean1()
return new ExampleBean();
这个BeanOneConfiguration
就是注解注入bean1
&#xff0c;然后看测试类&#xff1a;
&#64;Test
public void conditionalOnMissingBeanMatch() throws Exception
AnnotationConfigApplicationContext ctx &#61; new AnnotationConfigApplicationContext();
ctx.register(BeanOneConfiguration.class, BeanTwoConfiguration.class);
ctx.refresh();
assertTrue(ctx.containsBean("bean1"));
assertFalse(ctx.containsBean("bean2"));
assertFalse(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration"));
这样就没有注入configurationClassWithConditionTests.BeanTwoConfiguration
了&#xff0c;所以也不会注入bean2
。
上面这个是简单使用。下面我们就来其在SpringBoot
中的使用&#xff0c;这个其实我们上一篇文章有简单提到。下面这些注解都是用在SpringBoot的自动配置。
&#64;Target( ElementType.TYPE, ElementType.METHOD )
&#64;Retention(RetentionPolicy.RUNTIME)
&#64;Documented
&#64;Conditional(OnBeanCondition.class)
public &#64;interface ConditionalOnBean
Class<?>[] value() default ;
String[] type() default ;
...........
这个注解&#64;ConditionalOnBean
有点类似上面demo的NoBeanOneCondition
这种&#xff0c;只不过NoBeanOneCondition
是类&#xff0c;还要自动注入&#xff0c;这个&#64;ConditionalOnBean
只要注解标注就可以了。不过两者的核心都是通过&#64;Conditional
来导入Condition
接口的实现类。这里是导入的OnBeanCondition
。
然后这个&#64;ConditionalOnBean
只要是用来判断有没有Bean
&#xff0c;只有存在的话才注入。下面我们来看下使用demo
&#64;Configuration
&#64;ConditionalOnBean(name &#61; "foo")
protected static class OnBeanNameConfiguration
&#64;Bean
public String bar()
return "bar";
&#64;Configuration
protected static class FooConfiguration
&#64;Bean
public String foo()
return "foo";
&#64;Test
public void testNameOnBeanCondition()
this.contextRunner.withUserConfiguration(FooConfiguration.class, OnBeanNameConfiguration.class)
.run(this::hasBarBean);
private void hasBarBean(AssertableApplicationContext context)
assertThat(context).hasBean("bar");
assertThat(context.getBean("bar")).isEqualTo("bar");
这样就完成了OnBeanNameConfiguration
的注入&#xff0c;当然这里还可以指定这个Bean的类似&#xff0c;可以通过value
、或type
&#xff1a;
&#64;Configuration
&#64;ConditionalOnBean(name &#61; "foo", value &#61; Date.class)
protected static class OnBeanNameAndTypeConfiguration
&#64;Bean
public String bar()
return "bar";
&#64;Test
public void testNameAndTypeOnBeanCondition()
this.contextRunner.withUserConfiguration(FooConfiguration.class, OnBeanNameAndTypeConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
这样由于类型不匹配&#xff0c;就不能注入了。
当然SpringBoot
还有很多的这种注解&#xff0c;来帮助自动配置与注解。
看名称也大致知道其的作用了。我们下面就来看下OnBeanCondition
具体逻辑。
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition
它有继承SpringBootCondition
,然后实现ConfigurationCondition
。
public interface ConfigurationCondition extends Condition
ConfigurationPhase getConfigurationPhase();
enum ConfigurationPhase
PARSE_CONFIGURATION,
REGISTER_BEAN
这个接口主要是为了颗粒化控制Condition
的作用&#xff0c;要对应匹配才会返回true
。PARSE_CONFIGURATION
表面这个是Configuration
即配置类&#xff0c;REGISTER_BEAN
表示这个是一个常规Bean
。
public abstract class SpringBootCondition implements Condition
SpringBootCondition是SpringBoot
关于Condition
的基础接口&#xff0c;也就是说&#xff0c;SpringBoot
所有拓展Condition
的都是通过SpringBootCondition
来实现Condition
接口。下面我们就来具体看下其的方法。
&#64;Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
String classOrMethodName &#61; getClassOrMethodName(metadata);
try
ConditionOutcome outcome &#61; getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
catch (NoClassDefFoundError ex)
throw new IllegalStateException("Could not evaluate condition on " ....
&#43; "put a &#64;ComponentScan in the default package by mistake)", ex);
catch (RuntimeException ex)
throw new IllegalStateException("Error processing condition on " &#43; getName(metadata), ex);
这里首先是通过getMatchOutcome
方法来获取匹配结果&#xff0c;然后再通过recordEvaluation
来记录评估提交的详细结果&#xff0c;最后返回是否匹配。这里主要是getMatchOutcome
方法&#xff0c;因为整个逻辑判断是否匹配都是这个方法&#xff0c;其他方法主要是记录一些信息&#xff0c;我们就略过了。
public class ConditionOutcome
private final boolean match;
private final ConditionMessage message;
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
这个方法是抽象方法&#xff0c;要让子类来处理是否匹配。
下面我们就来具体看下这个SpringBootCondition
的一个子类OnBeanCondition
的实现逻辑。
&#64;Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata)
ConditionMessage matchMessage &#61; ConditionMessage.empty();
if (metadata.isAnnotated(ConditionalOnBean.class.getName()))
..........
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName()))
.........
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName()))
........
return ConditionOutcome.match(matchMessage);
这个我们可以看到其是会判断三种不同的注解ConditionalOnBean
、ConditionalOnSingleCandidate
、ConditionalOnMissingBean
。其中ConditionalOnSingleCandidate
是用来判断是不是传入的类是不是有单例Bean
、ConditionalOnMissingBean
是判断有没有缺失这个Bean
。
&#64;Target( ElementType.TYPE, ElementType.METHOD )
&#64;Retention(RetentionPolicy.RUNTIME)
&#64;Documented
&#64;Conditional(OnBeanCondition.class)
public &#64;interface ConditionalOnMissingBean
&#64;Target( ElementType.TYPE, ElementType.METHOD )
&#64;Retention(RetentionPolicy.RUNTIME)
&#64;Documented
&#64;Conditional(OnBeanCondition.class)
public &#64;interface ConditionalOnSingleCandidate
这里面的具体判断我们就先不深入探究了。
下面我们就来看下其是怎样使用的&#xff0c;我们在前面的两篇文章是有初步提到过得。
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION))
return;
..........
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass &#61; asSourceClass(configClass);
do
sourceClass &#61; doProcessConfigurationClass(configClass, sourceClass);
while (sourceClass !&#61; null);
this.configurationClasses.put(configClass, configClass);
这里就是在配置类的解析的前面以PARSE_CONFIGURATION
也就是解析配置类的方式。
public boolean shouldSkip(&#64;Nullable AnnotatedTypeMetadata metadata, &#64;Nullable ConfigurationPhase phase)
if (metadata &#61;&#61; null || !metadata.isAnnotated(Conditional.class.getName()))
return false;
if (phase &#61;&#61; null)
.......
List<Condition> conditions &#61; new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata))
.........
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions)
......
return false;
下面我们就以前面的demo1
为例&#xff0c;来看下这个过程。
if (metadata &#61;&#61; null || !metadata.isAnnotated(Conditional.class.getName()))
return false;
首先是判断这个配置类有没有&#64;Conditional
注解&#xff0c;如果没有就返回false
&#xff0c;主要在processConfigurationClass
就不会直接返回&#xff0c;是继续往下解析。
if (phase &#61;&#61; null)
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata))
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
下一步是类型phase
判断&#xff0c;如果为null
&#xff0c;就需要设置参数&#xff0c;这里的ConfigurationClassUtils.isConfigurationCandidate
我们前面文章讲过&#xff0c;就是判断有没有&#64;Component
、&#64;Bean
这些
static
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
public static boolean isConfigurationCandidate(AnnotationMetadata metadata)
....
// Any of the typical annotations found?
for (String indicator : candidateIndicators)
if (metadata.isAnnotated(indicator))
return true;
// Finally, let&#39;s look for &#64;Bean methods...
try
return metadata.hasAnnotatedMethods(Bean.class.getName());
return false;
是的话&#xff0c;就返回true
表面其是PARSE_CONFIGURATION
,不然的话就是一个普通的REGISTER_BEAN
然后再重新进入shouldSkip
方法&#xff0c;就能继续往下走了。
List<Condition> conditions &#61; new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata))
for (String conditionClass : conditionClasses)
Condition condition &#61; getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
这里就是获取&#64;Conditional
的值&#xff0c;也就是实现Condition
接口的类获取&#xff0c;然后通过getCondition
去实例化创建添加到conditions
中。例如我们的demo
中&#xff0c;这里获取的就是NoBeanOneCondition
for (Condition condition : conditions)
ConfigurationPhase requiredPhase &#61; null;
if (condition instanceof ConfigurationCondition)
requiredPhase &#61; ((ConfigurationCondition) condition).getConfigurationPhase();
if ((requiredPhase &#61;&#61; null || requiredPhase &#61;&#61; phase) && !condition.matches(this.context, metadata))
return true;
return false;
这里就是最后一步了&#xff0c;如果是ConfigurationCondition
类型&#xff0c;由于其本身是有getConfigurationPhase()
返回ConfigurationPhase
的&#xff0c;所以这里会判断&#xff0c;然后就上课关键的condition.matches
,就是调用Condition
的方法&#xff0c;如果不匹配就会true
&#xff0c;也就是注解return
&#xff0c;不就行后续解析了。不然就最后返回false
来继续解析。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass
var cpro_id = "u6885494";